#include "userprog/syscall.h"
#include "devices/input.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/vaddr.h"

static void syscall_handler (struct intr_frame *);
struct thread_file * find_file(int fd);
bool sys_create (const char *file, unsigned initial_size);
bool sys_remove(const char *file);
unsigned sys_tell(int fd);

void 
exit_special (void)
{
  thread_current()->pcb->exitcode = -1;
  thread_exit ();
}

static int 
get_user (const uint8_t *uaddr)
{
  int result;
  asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a" (result) : "m" (*uaddr));
  return result;
}

void * 
check_ptr(const void *vaddr)
{ 
  /* Judge address */
  if (!is_user_vaddr(vaddr))
  {
    exit_special ();
  }
  /* Judge the page */
  void *ptr = pagedir_get_page (thread_current()->pagedir, vaddr);///&:(70@
  if (!ptr)
  {
    exit_special ();
  }
  /* Judge the content of page */
  uint8_t *check_byteptr = (uint8_t *) vaddr;
  for (uint8_t i = 0; i < 4; i++) 
  {
    if (get_user(check_byteptr + i) == -1)
    {
      exit_special ();
    }
  }

  return ptr;
}

void 
check_buffer(const void *buffer, unsigned size) {
    const uint8_t *ptr = buffer;
    for (unsigned i = 0; i < size; ++i) {
        check_ptr(ptr + i);
    }
}

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f UNUSED) 
{
  uint32_t* p = f->esp;
  check_ptr(p+1);
  int syscall_number = *p;
  p++;
  switch (syscall_number) {
    case SYS_HALT:
    {
      sys_halt();
      NOT_REACHED();
      break;
    }
    case SYS_EXIT:
    {
      int status = *p;
      sys_exit(status);
      break;
    }
    case SYS_EXEC:
    {
      check_ptr(*p);
      char*cmd_line = (char*)*p;
      f->eax = sys_exec(cmd_line);
      break;
    }
    case SYS_WAIT:
    {
      pid_t pid = *p;
      f->eax = sys_wait(pid);
      break;
    }
    case SYS_CREATE:
    {
      check_ptr(*p);
      const char* file = (const char*)*p;
      unsigned initial_size = *(p+1);
      f->eax = sys_create(file,initial_size);
      break;
    }
    case SYS_REMOVE:
    {
      check_ptr(*p);
      const char *file = (const char*)*p;
      f->eax = sys_remove(file);
      break;
    }
    case SYS_OPEN:
    {
      check_ptr(*p);
      const char*file = (const char*)*p;
      f->eax = sys_open(file);
      break;
    }
    case SYS_FILESIZE:
    {
      int fd = *p;
      f->eax = sys_filesize(fd);
      break;
    }
    case SYS_READ:
    {      
      int fd = *p;
      char *buffer = (char*)*(p+1);
      unsigned size = *(p+2);
      check_buffer(buffer,size);
      f->eax = sys_read(fd,buffer,size);
      break;
    }
    case SYS_WRITE:
    {
      int fd = *p;
      const char * buffer = (const char *)*(p+1);
      unsigned size = *(p+2);
      check_buffer(buffer,size);
      f->eax = sys_write(fd,buffer,size);
      break;
    }
    case SYS_SEEK:
    {
      int fd = *p;
      unsigned position = *(p+1);
      sys_seek(fd,position);
      break;
    }
    case SYS_TELL:
    {
      int fd =*p;
      f->eax= sys_tell(fd);
      break;
    }
    case SYS_CLOSE:
    {
      int fd = *p;
      sys_close(fd);
      break;
    }
    default:
    printf("[ERROR] system call %d is unimplemented!\n", syscall_number);
    exit_special();
    break;
  }
}
/****************** System Call Implementations ********************/
void 
sys_halt (void)
{
  shutdown_power_off();
}

void 
sys_exit (int status)
{
  thread_current()->pcb->exitcode = status;
  thread_exit ();
}

pid_t 
sys_exec (const char* cmd_line)
{
  return process_execute(cmd_line);
}

int 
sys_wait (pid_t pid)
{
  return process_wait(pid);
}

bool 
sys_create (const char *file, unsigned initial_size)
{
  acquire_file_lock();
  bool success = filesys_create(file,initial_size);
  release_file_lock();
  return success;
}

bool 
sys_remove(const char *file)
{
  acquire_file_lock();
  bool success = filesys_remove(file);
  release_file_lock();
  return success;
}

int 
sys_open(const char*file)
{
  acquire_file_lock();
  struct file * open_file = filesys_open(file); 
  release_file_lock();
  struct thread *cur = thread_current();
  if(open_file){
    struct thread_file*temp = malloc(sizeof(struct thread_file));
    temp->fd = cur->next_fd++;
    temp->file = open_file;
    list_push_back (&cur->file_list, &temp->file_elem);
    return temp->fd;
  }
  else
  {
    return -1;
  }
}

int 
sys_filesize(int fd)
{
  struct thread_file*temp = find_file(fd);
  if(temp)
  {
    acquire_file_lock();
    int len = file_length(temp->file);
    release_file_lock();
    return len;
  }
  else
  {
    return -1;
  }
}

int 
sys_read(int fd, char*buffer, unsigned size)
{
  if(fd==0)
  {
    for(int i=0;i<size;i++)
    {
      buffer[i] = input_getc();
    }
    return size;
  }
  else
  {
    struct thread_file*temp = find_file(fd);
    if(temp)
    {
      acquire_file_lock();
      int res = file_read(temp->file,buffer,size);
      release_file_lock();
      return res;
    }
    else
    {
      return -1;
    }
  }
}

int 
sys_write (int fd,const void*buffer,unsigned size)
{
  if (fd == 1) {
    putbuf(buffer,size);
    return size;
  }
  else
  {
    struct thread_file * temp = find_file(fd);
    if (temp)
    {
      acquire_file_lock ();
      int res = file_write (temp->file, buffer, size);
      release_file_lock();
      return res;
    } 
    else
    {
      return 0;
    }
  }
}

void 
sys_seek(int fd,unsigned position)
{
  struct thread_file*temp = find_file(fd);
  if(temp)
  {
    acquire_file_lock();
    file_seek(temp->file,position);
    release_file_lock();
  }
}

unsigned 
sys_tell(int fd)
{
  struct thread_file*temp = find_file(fd);
  if(temp)
  {
    acquire_file_lock();
    int res = file_tell(temp->file);
    release_file_lock();
    return res;
  }
  else
  {
    return -1;
  }
}

void 
sys_close(int fd)
{
  struct thread_file*temp = find_file(fd);
  if(temp)
  {
    acquire_file_lock();
    file_close(temp->file);
    release_file_lock();
    list_remove(&temp->file_elem);
    free(temp);
  }
}

struct thread_file * 
find_file (int fd)
{
  struct list_elem *e;
  struct thread_file * temp = NULL;
  struct list *files = &thread_current ()->file_list;
  for (e = list_begin (files); e != list_end (files); e = list_next (e)){
    temp = list_entry (e, struct thread_file, file_elem);
    if (fd == temp->fd)
      return temp;
  }
  return NULL;
}